/*
 * Copyright (c) 2015-2020 ACOINFO Co., Ltd.
 * All rights reserved.
 *
 * Detailed license information can be found in the LICENSE file.
 *
 * File: ms_kmod_loader.c Kernel module loader.
 *
 * Author: Jiao.jinxing <jiaojinxing@acoinfo.com>
 *
 */

#define _GNU_SOURCE
#include "ms_kmod_loader_cfg.h"
#include "ms_shell_cfg.h"
#include "ms_io_cfg.h"
#include "ms_kern.h"
#include "tos_elfloader.h"
#include "ms_kmod_loader.h"

#include <string.h>
#include <stdlib.h>

/*
   libgcc.a libm.a depend

         w __cxa_begin_cleanup
         w __cxa_call_unexpected
         w __cxa_type_match
         w __gnu_Unwind_Find_exidx
         U __exidx_end
         U __exidx_start
         U __errno
         U _impure_ptr
         U abort
         U fflush
         U fprintf
         U malloc
         U memcpy
         U memset
 */
//MS_SYM_EXPORT(__errno); ms_libc_newlib.c exported
//MS_SYM_EXPORT(abort);   ms_libc_newlib.c exported
//MS_SYM_EXPORT(fprintf); libgcc.a __eprintf depend, mskmodlibc.a include __eprintf
//MS_SYM_EXPORT(fflush);  libgcc.a __eprintf depend, mskmodlibc.a include __eprintf

MS_SYM_EXPORT(_impure_ptr);
MS_SYM_EXPORT(malloc);
MS_SYM_EXPORT(free);
MS_SYM_EXPORT(calloc);
MS_SYM_EXPORT(realloc);

typedef struct ms_kmodule {
#if (MS_CFG_SHELL_MODULE_EN > 0) && (MS_CFG_KMOD_LOADER_SHELL_CMD_EN > 0)
    ms_list_head_t node;
    char name[MS_IO_NAME_BUF_SIZE];
#endif
    el_module_t el_module;
    ms_func_t exit;
} ms_kmodule_t;

extern unsigned long __ms_sym_tbl_start__;
extern unsigned long __ms_sym_tbl_end__;

#if (MS_CFG_SHELL_MODULE_EN > 0) && (MS_CFG_KMOD_LOADER_SHELL_CMD_EN > 0)
static ms_list_head_t ms_kmodule_list;
static ms_handle_t    ms_kmodule_lockid;

#define __MS_KMODULE_LOCK()      ms_mutex_lock(ms_kmodule_lockid, MS_TIMEOUT_FOREVER)
#define __MS_KMODULE_UNLOCK()    ms_mutex_unlock(ms_kmodule_lockid)
#endif

void *elfloader_symtab_lookup(char *name)
{
    const ms_symbol_t *sym_tbl = (ms_symbol_t *)&__ms_sym_tbl_start__;
    ms_uint32_t sym_nr = (((ms_addr_t)&__ms_sym_tbl_end__) - ((ms_addr_t)&__ms_sym_tbl_start__)) / sizeof(ms_symbol_t);
    ms_uint32_t start = 0U;
    ms_uint32_t end = sym_nr - 1U;
    ms_uint32_t mid;
    int rc;

    while (start <= end) {
        mid = (end - start) / 2U + start;
        rc = strcmp(name, sym_tbl[mid].name);
        if (rc < 0) {
            end = mid - 1U;
        } else if (rc > 0) {
            start = mid + 1U;
        } else {
            return sym_tbl[mid].addr;
        }
    }

    return MS_NULL;
}

ms_err_t ms_kmodule_loader_init(void)
{
#if (MS_CFG_SHELL_MODULE_EN > 0) && (MS_CFG_KMOD_LOADER_SHELL_CMD_EN > 0)
    MS_LIST_HEAD_INIT(&ms_kmodule_list);

    return ms_mutex_create("module_lock", MS_WAIT_TYPE_PRIO, &ms_kmodule_lockid);
#else
    return MS_ERR_NONE;
#endif
}

ms_err_t ms_kmodule_load(const char *path, ms_bool_t req_sym, ms_kmodule_handle_t *moduleid)
{
    ms_err_t err;

    if (moduleid != MS_NULL) {
        *moduleid = MS_NULL;
    }

    if ((path != MS_NULL) && (moduleid != MS_NULL)) {
        ms_kmodule_t *mod = ms_kmalloc(sizeof(struct ms_kmodule));
        if (mod != MS_NULL) {
            mod->el_module.fd = ms_io_open(path, O_RDONLY, 0666);
            if (mod->el_module.fd >= 0) {
                err = tos_elfloader_load(&mod->el_module, mod->el_module.fd);
                if (err == MS_ERR_NONE) {
                    ms_func_t init = tos_elfloader_find_symbol(&mod->el_module, "module_init");

                    mod->exit = tos_elfloader_find_symbol(&mod->el_module, "module_exit");

                    if (!req_sym) {
                        ms_io_close(mod->el_module.fd);
                        mod->el_module.fd = -1;
                    }

                    if (init != MS_NULL) {
                        init();
                    }

#if (MS_CFG_SHELL_MODULE_EN > 0) && (MS_CFG_KMOD_LOADER_SHELL_CMD_EN > 0)
                    const char *p = strrchr(path, '/');
                    if (p != MS_NULL) {
                        strlcpy(mod->name, p + 1, sizeof(mod->name));
                    } else {
                        strlcpy(mod->name, path, sizeof(mod->name));
                    }

                    __MS_KMODULE_LOCK();
                    ms_list_add_tail(&mod->node, &ms_kmodule_list);
                    __MS_KMODULE_UNLOCK();
#endif

                    *moduleid = mod;
                    err = MS_ERR_NONE;
                } else {
                    ms_io_close(mod->el_module.fd);
                    ms_kfree(mod);
                }
            } else {
                ms_kfree(mod);
                mod = MS_NULL;
                err = MS_ERR_IO_NO_EXISTED;
            }
        } else {
            err = MS_ERR_KERN_HEAP_NO_MEM;
        }
    } else {
        err = MS_ERR_ARG_NULL_PTR;
    }

    return err;
}

ms_err_t ms_kmodule_unload(ms_kmodule_handle_t moduleid)
{
    ms_kmodule_t *mod = moduleid;
    ms_err_t err;

    if (mod != MS_NULL) {
        if (mod->exit != MS_NULL) {
            mod->exit();
        }

        tos_elfloader_unload(&mod->el_module);

        if (mod->el_module.fd >= 0) {
            ms_io_close(mod->el_module.fd);
        }

#if (MS_CFG_SHELL_MODULE_EN > 0) && (MS_CFG_KMOD_LOADER_SHELL_CMD_EN > 0)
        __MS_KMODULE_LOCK();
        ms_list_del(&mod->node);
        __MS_KMODULE_UNLOCK();
#endif

        ms_kfree(mod);

        err = MS_ERR_NONE;
    } else {
        err = MS_ERR_ARG_NULL_PTR;
    }

    return err;
}

ms_ptr_t ms_kmodule_symbol(ms_kmodule_handle_t moduleid, const char *name)
{
    ms_kmodule_t *mod = moduleid;
    ms_ptr_t ret;

    if ((mod != MS_NULL) && (name != MS_NULL) && (mod->el_module.fd >= 0)) {
        ret = tos_elfloader_find_symbol(&mod->el_module, (char *)name);
    } else {
        ret = MS_NULL;
    }

    return ret;
}

#if (MS_CFG_SHELL_MODULE_EN > 0) && (MS_CFG_KMOD_LOADER_SHELL_CMD_EN > 0)

#include "ms_shell.h"

static void __ms_shell_kmod_insmod(int argc, char *argv[], const ms_shell_io_t *io)
{
    if (argc >= 2) {
        ms_kmodule_handle_t moduleid;
        ms_err_t err = ms_kmodule_load(argv[1], MS_FALSE, &moduleid);

        if (err == MS_ERR_NONE) {
            ms_shell_printf(io, "Kernel module handle %p\n", moduleid);

        } else {
            ms_shell_printf(io, "Failed to load kernel module %s, error=%d\n", argv[1], err);
        }
    } else {
        ms_shell_printf(io, "Please specify a kernel module handle!\n");
    }
}

static void __ms_shell_kmod_rmmod(int argc, char *argv[], const ms_shell_io_t *io)
{
    if (argc >= 2) {
        ms_kmodule_handle_t moduleid = (ms_kmodule_handle_t)ms_strtoul(argv[1U], MS_NULL, 16U);

        if (moduleid != MS_NULL) {
            ms_kmodule_unload(moduleid);

        } else {
            ms_shell_printf(io, "No such kernel module!\n");
        }
    } else {
        ms_shell_printf(io, "Argument error!\n");
    }
}

static void __ms_shell_kmod_ksyms(int argc, char *argv[], const ms_shell_io_t *io)
{
    const ms_symbol_t *sym;
    ms_uint32_t sym_nr = 0;

    for (sym = (ms_symbol_t *)&__ms_sym_tbl_start__;
         sym < (ms_symbol_t *)&__ms_sym_tbl_end__; sym++) {
        ms_shell_printf(io, "%s\n", sym->name);
        sym_nr++;
    }

    ms_shell_printf(io, "\nTotal symbol: %u\n\n", sym_nr);
}

static void __ms_shell_kmod_lsmod(int argc, char *argv[], const ms_shell_io_t *io)
{
    ms_list_head_t *itervar;
    ms_kmodule_t *mod;
    ms_uint32_t mod_nr = 0;

    ms_shell_printf(io, "\n"
                    "            NAME           HANDLE    BASE     SIZE   SYMCNT\n"
                    "------------------------- -------- -------- -------- ------\n");

    __MS_KMODULE_LOCK();

    ms_list_for_each(itervar, &ms_kmodule_list) {
        mod = MS_CONTAINER_OF(itervar, ms_kmodule_t, node);
        ms_shell_printf(io, "+ %-23s %08x %08x %8x %6u\n",
                        mod->name, (ms_addr_t)mod,
                        (ms_addr_t)mod->el_module.base, mod->el_module.size,
                        (unsigned)(mod->el_module.symtab_size / mod->el_module.symtab_entsize));

        mod_nr++;
    }

    __MS_KMODULE_UNLOCK();

    ms_shell_printf(io, "\nTotal modules: %d\n", mod_nr);
}

MS_SHELL_CMD(insmod, __ms_shell_kmod_insmod, "insmod path, load kernel module",  __ms_shell_cmd_insmod);
MS_SHELL_CMD(rmmod,  __ms_shell_kmod_rmmod,  "rmmod path, unload kernel module", __ms_shell_cmd_rmmod);
MS_SHELL_CMD(ksyms,  __ms_shell_kmod_ksyms,  "ksyms, show kernel symbol name",   __ms_shell_cmd_ksyms);
MS_SHELL_CMD(lsmod,  __ms_shell_kmod_lsmod,  "lsmod, show all kernel modules",   __ms_shell_cmd_lsmod);

#endif
